package personnel.department.utils;

import static com.google.common.base.Preconditions.checkNotNull;

import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.joda.time.Days;
import org.joda.time.Interval;
import org.joda.time.LocalDate;

import com.google.common.base.Preconditions;

public class DateUtils {

	private static final String TIME_FORMAT = "HH:mm";
	private static final String BB_DATE_FORMAT = "yyyy-MM-dd";

	public static final long MILLIS_IN_DAY = 24L * 60 * 60 * 1000;
	public static final long MILLIS_IN_HOUR = 60 * 60 * 1000;
	public static final long MILLIS_IN_MINUTE = 60 * 1000;

	private static final String SHORT_DOW_FORMAT = "E";
	private static final String FULL_DOW_FORMAT = "EEEE";

	public static final List<Integer> WORK_DAYS = Arrays.asList(Calendar.MONDAY, Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY);

	/**
	 *
	 * @param date
	 * @param time (in minutes)
	 * @return date
	 */
	public static Date createDateTime(Date date, int time) {
		if (date == null)
			return null;
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.set(Calendar.HOUR_OF_DAY, time / 60);
		calendar.set(Calendar.MINUTE, time % 60);
		calendar.add(Calendar.SECOND, 0);
		calendar.add(Calendar.MILLISECOND, 0);
		return calendar.getTime();
	}



	/**
	 * Add days and truncate time to 00:00
	 * @param date
	 * @param days
	 * @return
	 */
	public static Date addDays(Date date, int days) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		calendar.add(Calendar.DAY_OF_YEAR, days);
		return calendar.getTime();
	}

	public static Date addHours(Date time, int hours) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(time);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		calendar.add(Calendar.HOUR_OF_DAY, hours);
		return calendar.getTime();
	}

	public static Date add(Date date, int field, int amount) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.add(field, amount);
		return calendar.getTime();
	}

	public static Date roll(Date date, int field, int amount) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.roll(field, amount);
		return calendar.getTime();
	}

	public static Date createDateTime(Date date, Date time) {
		Calendar calendarTo = Calendar.getInstance();
		calendarTo.setTime(date);
		Calendar calendarFrom = Calendar.getInstance();
		calendarFrom.setTime(time);
		calendarTo.set(Calendar.HOUR_OF_DAY, calendarFrom.get(Calendar.HOUR_OF_DAY));
		calendarTo.set(Calendar.MINUTE, calendarFrom.get(Calendar.MINUTE));
		calendarTo.set(Calendar.SECOND, 0);
		calendarTo.set(Calendar.MILLISECOND, 0);
		return calendarTo.getTime();
	}

	public static Date getNextMidnight() {
		Date date = addDays(Calendar.getInstance().getTime(), 1);
		return date;
	}

	public static int getNights(Date startDate, Date endDate) {
		if (startDate == null || endDate == null)
			return 0;

		if (startDate.after(endDate))
			return 0;

		LocalDate from = LocalDate.fromDateFields(startDate);
		LocalDate to = LocalDate.fromDateFields(endDate);

		return Days.daysBetween(from, to).getDays();
	}

	public static int getNights(Calendar startDate, Calendar endDate) {
		if (startDate == null || endDate == null)
			return 0;

		if (startDate.after(endDate))
			return 0;

		LocalDate from = LocalDate.fromCalendarFields(startDate);
		LocalDate to = LocalDate.fromCalendarFields(endDate);

		return Days.daysBetween(from, to).getDays();
	}

	/**
	 * Old version of {@link #getNights(Calendar, Calendar)} method. Used for
	 * tests
	 *
	 * @param startDate
	 * @param endDate
	 * @return
	 * @deprecated use {@link #getNights(Calendar, Calendar)} instead
	 */
	@Deprecated
	public static int getNightsLegacy(Calendar startDate, Calendar endDate) {
		if (startDate == null || endDate == null)
			return 0;
		Calendar start = extractDate((Calendar) startDate.clone());
		Calendar end = extractDate((Calendar) endDate.clone());
		int nights = 0;
		while (end.after(start)) {
			nights++;
			start.add(Calendar.DATE, 1);
		}
		return nights;
	}

	public static int getDays(Date startDate, Date endDate) {
		if (startDate == null || endDate == null)
			return 0;
		Calendar start = Calendar.getInstance();
		start.setTime(truncate((Date) startDate.clone(), Calendar.MINUTE));
		Calendar end = Calendar.getInstance();
		end.setTime(truncate((Date) endDate.clone(), Calendar.MINUTE));
		int days = 0;
		while (end.after(start)) {
			days++;
			start.add(Calendar.DATE, 1);
		}
		return days;
	}

	/**
	 * Remove information about time. If given date is <code>null</code> returns <code>null</code>
	 *
	 * @param date
	 *            can be null
	 * @return
	 */
	public static Date extractDate(Date date) {
		if (date == null)
			return null;

		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		return extractDate(calendar).getTime();
	}

	public static Calendar extractDate(Calendar date) {
		if (date == null)
			return null;
		// Calendar.set is quicker than
		// org.apache.commons.lang.time.DateUtils.truncate
		date.set(Calendar.HOUR_OF_DAY, 0);
		date.set(Calendar.MINUTE, 0);
		date.set(Calendar.SECOND, 0);
		date.set(Calendar.MILLISECOND, 0);
		return date;
	}

	public static Date clearSeconds(Date date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		return calendar.getTime();
	}

	public static Date extractTime(Date date) {
		Calendar from = Calendar.getInstance();
		from.setTime(date);

		Calendar result = Calendar.getInstance();
		result.setTimeInMillis(0L);
		result.set(Calendar.HOUR_OF_DAY, from.get(Calendar.HOUR_OF_DAY));
		result.set(Calendar.MINUTE, from.get(Calendar.MINUTE));
		return result.getTime();
	}

	/**
	 * Used in scripts only to format date for BlackBox
	 *
	 * @param date
	 * @return formatted date in format yyyy-MM-dd
	 */
	public static String dateToString(Date date) {
		return new SimpleDateFormat(BB_DATE_FORMAT).format(date);
	}

	public static Date stringToDate(String string) {
		try {
			return new SimpleDateFormat(BB_DATE_FORMAT).parse(string);
		} catch (ParseException e) {
			return null;
		}
	}

	public static Date stringToTime(String string) {
		try {
			return new SimpleDateFormat(TIME_FORMAT).parse(string);
		} catch (ParseException e) {
			return null;
		}
	}

	/**
	 * Returns true if the time is between from and to. Note: it returns true if time == from or time == to
	 *
	 * @param time
	 * @param from
	 * @param to
	 * @return
	 */
	public static boolean timeBetweenOrEquals(Date time, Date from, Date to) {
		return DateUtils.timeBetween(time, from, to) || DateUtils.timeEquals(from, time) || DateUtils.timeEquals(to, time);
	}

	/**
	 * Returns true if the time is between from and to. Note: it returns false if time == from or time == to
	 *
	 * @param time
	 * @param from
	 * @param to
	 * @return
	 */
	public static boolean timeBetween(Date time, Date from, Date to) {
		time = extractTime(time);
		from = extractTime(from);
		to = extractTime(to);

		if (to.before(from)) {
			Calendar timeCal = Calendar.getInstance();
			timeCal.setTime(time);
			if (timeCal.get(Calendar.HOUR_OF_DAY)*60 + timeCal.get(Calendar.MINUTE) > 12*60) {
				Calendar toCal = Calendar.getInstance();
				toCal.setTime(to);
				toCal.add(Calendar.DAY_OF_YEAR, 1);
				to = toCal.getTime();
			}
			else {
				Calendar fromCal = Calendar.getInstance();
				fromCal.setTime(from);
				fromCal.add(Calendar.DAY_OF_YEAR, -1);
				from = fromCal.getTime();
			}
		}

		if (from.before(time) && to.after(time))
			return true;

		return false;
	}

	/**
	 * Compares time part of dates
	 *
	 * @param date
	 * @param other
	 * @return
	 */
	public static boolean timeEquals(Date date, Date other) {
		Preconditions.checkNotNull(date);
		Preconditions.checkNotNull(other);

		date = extractTime(date);
		other = extractTime(other);

		return date.equals(other);
	}

	public static boolean isMiddayTime(Date date) {
		date = extractTime(date);

		Calendar c = Calendar.getInstance();
		c.setTime(date);

		return c.get(Calendar.HOUR_OF_DAY) == 12 && c.get(Calendar.MINUTE) == 0;
	}

	public static boolean dateBetween(Date orig, Date from, Date to) {
		// TODO: switch implementation to #dateBetween(LocalDate) as it seems to
		// work faster when called from several threads simultaneously
		Preconditions.checkNotNull(orig);
		Preconditions.checkNotNull(from);
		Preconditions.checkNotNull(to);

		orig = extractDate(orig);
		from = extractDate(from);
		to = extractDate(to);

		return from.compareTo(orig) <= 0 && to.compareTo(orig) >= 0;
	}

	/**
	 * Same as {@link #dateBetween(Date, Date, Date)} but works faster with multithreading
	 *
	 * @param date
	 * @param fromDate
	 * @param toDate
	 * @return
	 */
	public static boolean dateBetween(LocalDate date, Date fromDate, Date toDate) {
		Preconditions.checkNotNull(date);
		Preconditions.checkNotNull(fromDate);
		Preconditions.checkNotNull(toDate);

		return new LocalDate(fromDate).compareTo(date) <= 0 && new LocalDate(toDate).compareTo(date) >= 0;
	}

	/**
	 * <b>WARNING</b> month is <b>NOT</b> <code>Calendar</code> constant. it is 1 based number of month in year FIXME 2AG: ??? see {@link Calendar#set(int, int, int)} : "Month value is 0-based. e.g.,
	 * 0 for January" !
	 *
	 * @param day
	 * @param month
	 * @param year
	 * @return
	 */
	public static Date date(int day, int month, int year) {
		Calendar result = Calendar.getInstance();
		result.setTimeInMillis(0L);
		// result.set(year, month + 1, day); XXX 2AG: ??? if in outer code
		// month=1 for JAN, then
		// here you will do in calendar (1+1) = 2 = MAR !
		result.set(year, month - 1, day); // Changed by SI. If it is wrong,
		// correct, please.
		return result.getTime();
	}

	public static Date time(int hour, int minute) {
		Calendar result = Calendar.getInstance();
		result.setTimeInMillis(0L);

		result.set(Calendar.HOUR_OF_DAY, hour);
		result.set(Calendar.MINUTE, minute);

		return result.getTime();
	}

	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
		System.out.println(getNights(new Date(), new Date()));
		System.out.println(getNights(new Date(2007, 7, 1, 17, 35, 15), new Date(2007, 7, 1, 17, 36, 15)));
		System.out.println(getNights(new Date(2007, 7, 1, 17, 35, 15), new Date(2007, 7, 2, 17, 36, 15)));
		System.out.println(getNights(new Date(2007, 7, 1, 17, 35, 15), new Date(2007, 7, 1, 17, 35, 16)));

		System.out.println(getNights(createCalendar(new Date(2007 - 1900, 12 - 1, 3, 8 - 1, 5 - 1, 30)), createCalendar(new Date(2007 - 1900, 12 - 1, 10, 7 - 1, 15 - 1, 20))));
	}

	public static Date ceiling(Date time) {
		if (time == null)
			return null;

		Calendar cal = Calendar.getInstance();
		cal.setTime(time);

		ceiling(cal);

		return cal.getTime();
	}

	public static void ceiling(Calendar cal) {
		cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 23, 59, 59);
		cal.set(Calendar.MILLISECOND, 0);
	}

	/**
	 * Returns lower and upper bound of the intersection of the given dates ranges or empty array if they don't intersect
	 *
	 * @param fromDate1
	 * @param toDate1
	 * @param fromDate2
	 * @param toDate2
	 * @return empty array if there's not intersection or array with two date (startDate, endDate) if intersection exists
	 */
	public static Date[] intersect(Date fromDate1, Date toDate1, Date fromDate2, Date toDate2) {
		Calendar cache = Calendar.getInstance();

		fromDate1 = extractDate(fromDate1, cache);
		fromDate2 = extractDate(fromDate2, cache);
		toDate1 = extractDate(toDate1, cache);
		toDate2 = extractDate(toDate2, cache);

		Date fromDate = getMaxDate(fromDate1, fromDate2, false);
		Date toDate = getMinDate(toDate1, toDate2, false);

		if (fromDate == null || toDate == null)
			return new Date[0];

		boolean intersectionExists = fromDate.compareTo(toDate) <= 0;
		return intersectionExists ? new Date[] { fromDate, toDate } : new Date[0];
	}

	private static Date extractDate(Date date, Calendar cache) {
		if (date == null)
			return null;

		cache.setTime(date);
		return extractDate(cache).getTime();
	}

	public static boolean isIntersect(Date fromDate1, Date toDate1, Date fromDate2, Date toDate2) {
		return intersect(fromDate1, toDate1, fromDate2, toDate2).length > 0;
	}

	public static Date getMinDate(Date date1, Date date2) {
		return getMinDate(date1, date2, true);
	}

	public static Date getMinDate(Date date1, Date date2, boolean clearTime) {
		if (date1 == null)
			return date2;

		if (date2 == null)
			return date1;

		if (clearTime) {
			date1 = extractDate(date1);
			date2 = extractDate(date2);
		}

		return date1.compareTo(date2) < 0 ? date1 : date2;
	}

	public static Date getMaxDate(Date date1, Date date2, boolean truncateToDay) {
		if (date1 == null)
			return date2;

		if (date2 == null)
			return date1;

		if (truncateToDay) {
			date1 = extractDate(date1);
			date2 = extractDate(date2);
		}

		return date1.compareTo(date2) > 0 ? date1 : date2;
	}

	public static Date getMaxDate(Date date1, Date date2) {
		return getMaxDate(date1, date2, true);
	}

	public static void floor(Calendar cal) {
		cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
		cal.set(Calendar.MILLISECOND, 0);
	}

	public static Date floor(Date time) {
		if (time == null)
			return null;
		Calendar cal = Calendar.getInstance();
		cal.setTime(time);

		DateUtils.floor(cal);

		return cal.getTime();
	}

	public static void round(Calendar cal) {
		if (cal.get(Calendar.HOUR_OF_DAY) >= 12)
			cal.add(Calendar.DAY_OF_MONTH, 1);

		cal.set(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 0, 0, 0);
		cal.set(Calendar.MILLISECOND, 0);
	}

	public static String formatTime(Date time) {
		if (time == null)
			return null;

		return new SimpleDateFormat(TIME_FORMAT).format(time);
	}

	public static Calendar roundedCalendar() {
		Calendar cal = Calendar.getInstance();
		DateUtils.round(cal);
		return cal;
	}

	public static Date getDate(int year, int month, int day) {
		Calendar cal = Calendar.getInstance();
		cal.set(year, month, day, 0, 0, 0);
		cal.set(Calendar.MILLISECOND, 0);
		return cal.getTime();
	}

	/**
	 * returns true if time between two dates less than one day. Note that result comparing depends on TIME between dates. So dates 01/01/06 18:00 and 02/01/06 10:00 will be equals
	 * <p>
	 * TODO add version of this method with precision param. for example Calendar.DAY_OF_MONTH or Calendar.MINUTE
	 * </p>
	 * <p>
	 * TODO add analog of this method which compares depend on differense in field (in this case dates 01/01/06 18:00 and 02/01/06 10:00 not equals)
	 * </p>
	 * <p>
	 * TODO add version with variation param
	 * </p>
	 *
	 * @param lhs
	 * @param rhs
	 */
	public static boolean inexactEquals(Date lhs, Date rhs) {
		long d1 = lhs.getTime();
		long d2 = rhs.getTime();

		long diff = Math.abs(d1 - d2);
		return diff < MILLIS_IN_DAY;
	}

	public static Calendar createCalendar(Date date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		return calendar;
	}

	/**
	 * truncate(28 Mar 2002 13:45:01.231, Calendar.HOUR) == 28 Mar 2002 13:00:00.000
	 *
	 * The following fields are NOT supported:
	 * Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK, Calendar.DAY_OF_WEEK_IN_MONTH
	 *
	 * @param date
	 * @param field
	 * @return
	 */
	public static Date truncate(Date date, int field) {
		return org.apache.commons.lang3.time.DateUtils.truncate(date, field);
	}

	public static Date sum(Date date, Date date2) {
		return add(date, Calendar.MILLISECOND, (int) date2.getTime());
	}

	public static int getAge(Date dob, Date today) {
		dob = DateUtils.truncate(dob, Calendar.DAY_OF_MONTH);
		today = DateUtils.truncate(today, Calendar.DAY_OF_MONTH);

		Calendar calendar = Calendar.getInstance();
		calendar.setTime(dob);

		int age = 0;
		while (true) {
			calendar.add(Calendar.YEAR, 1);
			if (calendar.getTime().compareTo(today) > 0) {
				break;
			}

			++age;
		}

		return age;
	}

	public static int getAgeForToday(Date dob) {
		return getAge(dob, new Date());
	}

	/**
	 * WARNING do not use this method from groovy scripts
	 *
	 * creates date from string. examples: <br>
	 * <code>DateUtils.date("17.10.2007")</code> <br>
	 * <code>DateUtils.date("17.10.2007 21:00")</code> <br>
	 * <code>DateUtils.date("21:00")</code> <br>
	 * This method also supports using of '/' and '-' instead of '.' in date
	 * declaration
	 *
	 * @param date
	 * @return
	 */
	public static Date date(String date) {
		date = date.replace('/', '.').replace('-', '.');

		try {
			SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy HH:mm");
			return format.parse(date);
		} catch (ParseException e) {
		}

		try {
			SimpleDateFormat format = new SimpleDateFormat(TIME_FORMAT);
			return format.parse(date);
		} catch (ParseException e) {
		}

		try {
			SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy");
			return format.parse(date);
		} catch (ParseException e) {
		}

		throw new IllegalArgumentException("bad formatted date '" + date + "'");
	}

	public static boolean isDateBandCorrect(Date d1, Date d2) {
		return d1 != null && d2 != null && beforeOrEqual(d1, d2);
	}

	public static boolean beforeOrEqual(Date d1, Date d2) {
		return d1.getTime() <= d2.getTime();
	}

	public static boolean afterOrEqual(Date d1, Date d2) {
		return d1.getTime() >= d2.getTime();
	}

	public static Date set(Date date, int field, int value) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		cal.set(field, value);
		return cal.getTime();
	}

	public static boolean isSameDay(Date d1, Date d2) {
		if (d1 == null)
			return d2 == null;
		if (d2 == null)
			return d1 == null;
		return org.apache.commons.lang3.time.DateUtils.isSameDay(d1, d2);
	};

	public static boolean isSameDayAndTime(Date d1, Date d2) {
		if (d1 == null)
			return d2 == null;
		if (d2 == null)
			return false;
		Date date1 = DateUtils.truncate(d1, Calendar.MINUTE);
		Date date2 = DateUtils.truncate(d2, Calendar.MINUTE);
		return date1.equals(date2);
	};

	public static boolean dayBeforeOrEqual(Date d1, Date d2) {
		return beforeOrEqual(extractDate(d1), extractDate(d2));
	}

	public static boolean dayAfterOrEqual(Date d1, Date d2) {
		return afterOrEqual(extractDate(d1), extractDate(d2));
	}

	public static Date getDefaultBirthday(Date today, int years) {
		Calendar cal = createCalendar(today);
		org.apache.commons.lang3.time.DateUtils.truncate(cal, Calendar.DAY_OF_MONTH);
		cal.add(Calendar.YEAR, -years);
		cal.set(Calendar.DAY_OF_MONTH, 1);
		cal.set(Calendar.MONTH, Calendar.JANUARY);
		return cal.getTime();
	}

	/**
	 * Compares dates with each other
	 *
	 * @param d1
	 *            first date
	 * @param d2
	 *            second date
	 * @return negative value if d1 < d2, positive value if d1 > d2, 0 if they are equal
	 */
	public static final int compareDates(Date d1, Date d2) {
		if (d1 == d2)
			return 0;
		if (d1 != null && d2 == null) {
			return 1;
		} else if (d2 != null && d1 == null) {
			return -1;
		}
		return extractDate(d1).compareTo(extractDate(d2));
	}

	public static Date tomorrow() {
		return addDays(new Date(), 1);
	}

	/**
	 * @return today date (without time)
	 */
	public static Date today() {
		return extractDate(Calendar.getInstance()).getTime();
	}

	public static Date now() {
		return new Date();
	}

	/**
	 * Detect day of week by given date
	 *
	 * @param date
	 *            date to detect
	 * @return day of week constant
	 */
	public static int getDOW(Date date) {
		if (date == null)
			return 0;
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		return calendar.get(Calendar.DAY_OF_WEEK);
	}

	/**
	 * Get full name of the day represented by given date.
	 *
	 * @param date
	 *            - date to get day of week for.
	 * @return full name of the day for given date.
	 */
	public static String getDOWFullStr(Date date) {
		if (date == null)
			return "";
		return new SimpleDateFormat(FULL_DOW_FORMAT).format(date);
	}

	/**
	 * Get short name of the day represented by given date.
	 *
	 * @param date
	 *            - date to get day of week for.
	 * @return short name of the day for given date.
	 */
	public static String getDOWShortStr(Date date) {
		if (date == null)
			return "";
		return new SimpleDateFormat(SHORT_DOW_FORMAT).format(date);
	}

	/**
	 * Converts SQL time stamp data type to java {@link Date}
	 *
	 * @param timestamp
	 *            SQL time stamp. Can be null
	 * @return new {@link Date} object
	 */
	public static final Date toDate(Timestamp timestamp) {
		if (timestamp == null)
			return null;
		return new Date(timestamp.getTime() + (timestamp.getNanos() / 1000000));
	}

	public static final boolean isInDateRange(Date date, Date lower, Date upper) {
		if (date == null)
			return true;
		if (lower == null && upper == null)
			return true;

		if (lower == null) {
			return beforeOrEqual(date, upper);
		}

		if (upper == null) {
			return beforeOrEqual(lower, date);
		}

		return beforeOrEqual(lower, date) && beforeOrEqual(date, upper);
	}

	public static final boolean isInDateRange(Date fromDate, Date toDate, Date lower, Date upper) {
		if (lower != null) {
			if (fromDate == null)
				return false;
			if (!beforeOrEqual(lower, fromDate))
				return false;
		}
		if (upper != null) {
			if (toDate == null)
				return false;
			if (!DateUtils.afterOrEqual(upper, toDate))
				return false;
		}
		return true;
	}

	/**
	 * see rollToBeginOfWeek(Calendar cal)
	 *
	 * @param date
	 * @return
	 */
	public static Date rollToBeginOfWeek(Date date) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		rollToBeginOfWeek(cal);
		return cal.getTime();
	}

	/**
	 * Floor date to the nearest first day of date's week. Time part of date is not changed.
	 *
	 * @param date
	 * @return first day at date's week
	 */
	public static void rollToBeginOfWeek(Calendar cal) {
		cal.set(Calendar.DAY_OF_WEEK, cal.getFirstDayOfWeek());
	}

	/**
	 * see rollToEndOfWeek(Calendar cal)
	 *
	 * @param date
	 * @return
	 */
	public static Date rollToEndOfWeek(Date date) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		rollToEndOfWeek(cal);
		return cal.getTime();
	}

	/**
	 * Ceiling date to the last day of date's week. Time part of date is not changed.
	 *
	 * @param date
	 * @return first day at date's week
	 */
	public static void rollToEndOfWeek(Calendar cal) {
		cal.add(Calendar.WEEK_OF_YEAR, 1);
		rollToBeginOfWeek(cal);
		cal.add(Calendar.DAY_OF_YEAR, -1);
	}

	/**
	 * Return last day of date's month
	 *
	 * @param cal
	 */
	public static void rollToLastDayOfMonth(Calendar cal) {
		cal.add(Calendar.MONTH, 1);
		cal.set(Calendar.DAY_OF_MONTH, 1);
		cal.add(Calendar.DAY_OF_YEAR, -1);
	}

	public static boolean between(Date date, Date fromDate, Date toDate) {
		return (fromDate.getTime() <= date.getTime() && date.getTime() <= toDate.getTime());
	}

	/**
	 * Returns annual insurance to date, by from date.
	 */
	public static Date getToDateForAnnualInsurance(Date fromDate) {
		Preconditions.checkState(truncateTimeInDate(fromDate).equals(fromDate), "Only date without time accepted");

		Date fromDatePlusYear = DateUtils.add(fromDate, Calendar.YEAR, 1);
		Calendar instance = Calendar.getInstance();
		instance.setTime(fromDate);
		if (is29OfFebrary(instance)) {
			return fromDatePlusYear;
		}
		return DateUtils.addDays(fromDatePlusYear, -1);
	}

	private static Date truncateTimeInDate(Date fromDate) {
		return extractDate(fromDate);
	}

	private static boolean is29OfFebrary(Calendar instance) {
		return instance.get(Calendar.MONTH) == Calendar.FEBRUARY && instance.get(Calendar.DAY_OF_MONTH) == 29;
	}

	/**
	 * Return period for insurance by from and to date.
	 */
	public static int getPeriodForInsurance(Date fromDate, Date toDate) {
		return DateUtils.getNights(fromDate, toDate) + 1;
	}

	/**
	 * Returns the first day of next month. Time is truncated.
	 *
	 * @return date
	 */
	public static Date getFirstDayOfNextMonth() {
		Calendar cal = org.apache.commons.lang3.time.DateUtils.truncate(Calendar.getInstance(), Calendar.MONTH);
		cal.add(Calendar.MONTH, 1);
		return cal.getTime();
	}

	/**
	 * Returns the last day of next month. Time is truncated.
	 *
	 * @return date
	 */
	public static Date getLastDayOfPreviousMonth() {
		Calendar cal = org.apache.commons.lang3.time.DateUtils.truncate(Calendar.getInstance(), Calendar.MONTH);
		cal.add(Calendar.DAY_OF_YEAR, -1);
		return cal.getTime();
	}

	public static Date lastNightToCheckout(Date lastNight) {
		return DateUtils.addDays(lastNight, 1);
	}

	public static Date checkoutToLastNight(Date checkOut) {
		return DateUtils.addDays(checkOut, -1);
	}

	/**
	 * Returns interval containing all days from firstDay till lastDay
	 * (including). Interval ends are: start of firstDay and end of last day
	 *
	 * @param firstDay
	 * @param lastDay
	 * @return
	 */
	public static Interval interval(LocalDate firstDay, LocalDate lastDay) {
		return new Interval(firstDay.toDateTimeAtStartOfDay(), lastDay.plusDays(1).toDateTimeAtStartOfDay());
	}

	public static Date shiftToWorkDay(Date date) {
		checkNotNull(date);
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
		switch (dayOfWeek) {
		case Calendar.SATURDAY:
			calendar.add(Calendar.DAY_OF_YEAR, 2);
			break;
		case Calendar.SUNDAY:
			calendar.add(Calendar.DAY_OF_YEAR, 1);
			break;
		}
		return calendar.getTime();
	}

}